/*

This file is part of org-json.

org-json is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

*/

using System;
using System.Text.RegularExpressions;
using System.Reflection;
using System.IO;
using System.Collections.Generic;
using System.Text;
using Org.JSON.Output;

namespace Org.JSON
{
	public abstract class Value
	{
		//Create a regex from embeed resource
		private static Regex LexRegex = new Regex(
			new StreamReader(
				Assembly
					.GetExecutingAssembly()
					.GetManifestResourceStream("Org.JSON.Lexer.txt")
			)
			.ReadToEnd(),
			RegexOptions.Compiled
		);
		

		public static Value From(string json)
		{
			var index = 0;
			var queue = new Queue<string>();
			
			foreach(Match match in LexRegex.Matches(json))
			{
				if(match.Index != index)
					throw new LexicalExeption(index);
				else
					index += match.Length;
				
				if(new Regex("^\\s").Match(match.Value).Length == 0)
								queue.Enqueue(match.Value);
				
			}
			if(queue.Count == 0)
				throw new LexicalExeption(0);
			
			if(index < json.Length)
				throw new Exception("Expected by the end of json in the character " + index + ".");
			
			var rv = Analize(queue);
			
			if(queue.Count > 0)
			{
				int end = 0;
				while(queue.Count > 0)
					end += queue.Dequeue().Length;
				
				throw new Exception("Expected by the end of json in the character " + (json.Length - end) + ".");
			}
			
			return rv;
		}
		
		private static Value Analize(Queue<string> queue)
		{
			var current = queue.Dequeue();
			
			if(current == "null")
			{
				return new Null();
			}
			else if(current == "true")
			{
				return new Boolean(true);
			}
			else if(current == "false")
			{
				return new Boolean(false);
			}
			else if(current == "[")
			{
				return AnalizeArray(queue);
			}
			else if(current.StartsWith("\""))
			{
				return ParseString(current);
			}
			else if(current == "{")
			{
				return AnalyzeObject(queue);
			}
			else if( new List<char> { '0','1','2','3','4','5','6','7','8','9','-'}.Contains(current[0]))
			{
				return new Number(double.Parse(current.Replace(".",",")));
			}
			else
				throw new Exception("Token no manejado");
		}
		
		private static Array AnalizeArray(Queue<string> queue)
		{
			var rv = new Array();
			var first = true;
			
			while(queue.Peek() != "]")
			{
				if(first)
					first = false;
				else if(queue.Dequeue() != ",")
					throw new Exception("Se esperaba ',' entre elementos de un array");
				
				rv.Add(Analize(queue));
			}
			queue.Dequeue();	
			
			return rv;
		}
		
		private static Object AnalyzeObject(Queue<string> queue)
		{
			var rv = new Object();
			var first = true;
			
			while(queue.Peek() != "}")
			{
				if(first)
					first = false;
				else if(queue.Dequeue() != ",")
					throw new Exception("Se esperaba ',' entre elementos de un objeto");
				
				var key = ParseString(queue.Dequeue());
				
				if(queue.Dequeue() != ":")
					throw new Exception("Se esperaba ':' entre elementos de un array");
				
				rv.Add(key.Value,Analize(queue));
				
				//rv.Add(key, Analize(queue));
			}
			queue.Dequeue();	
			
			return rv;
		}
		
		private static String ParseString(string encoded)
		{
			var aux = new StringBuilder();
			
			for(int i = 1; i < encoded.Length - 1; i++)
			{
				var chr = encoded[i];
				
				if(chr == '\\')
				{
					chr = encoded[++i];
					if(chr == '\\' || chr == '\"' || chr == '/')
						aux.Append(chr);
					else if(chr == 'n')
					{
						aux.Append("\n");
					}
					else if(chr == 'n')
					{
						aux.Append("\n");
					}
					else if(chr == 'f')
					{
						aux.Append("\f");
					}
					else if(chr == 'r')
					{
						aux.Append("\r");
					}
					else if(chr == 't')
					{
						aux.Append("\t");
					}
					else if(chr == 'b')
					{
						aux.Append("\b");
					}
					else if(chr == 'u')
					{
						aux.Append(((char)int.Parse(encoded.Substring(i + 1, 4), System.Globalization.NumberStyles.AllowHexSpecifier)));
						i += 4;
					}
					else
					{
						throw new Exception("Invalid scape sequence");
					}
					
				}
				else
				{
					aux.Append(chr);
				}				
			}
			
			return new String(aux.ToString());
		}
		
		public override string ToString()
		{
			var output = new StringOutput();
			Serialize(output);
			return output.ToString();
		}
		
		public static implicit operator Value(string source)
		{
			if(source == null)
				return new Null();
			else
				return new String(source);
		}
		
		public static implicit operator Value(double source)
		{
			return new Number(source);
		}
		
		public static implicit operator Value(bool source)
		{
			return new Boolean(source);
		}
		
		
		public abstract void Serialize(Writeable output);

		//as aliases

		public JSON.String AsString
		{
			get
			{
				return this as JSON.String;
			}
		}

		public JSON.Number AsNumber
		{
			get
			{
				return this as JSON.Number;
			}
		}

		public JSON.Object AsObject
		{
			get
			{
				return this as JSON.Object;
			}
		}

		public JSON.Array AsArray
		{
			get
			{
				return this as JSON.Array;
			}
		}

		public JSON.Boolean AsBoolean
		{
			get
			{
				return this as JSON.Boolean;
			}
		}
	}
}
